﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using Mesuro.Common;
using Mesuro.Graphing.Utilities;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Shapes;
using Vector = Mesuro.Common.Vector;

namespace Mesuro.Graphing {
	public abstract class AbstractAxes : MultiAxes {
		//TODO: Fix scale padding for text
		//TODO: Contour plotting

		public static ISeries GetOriginalSeries( Plot plot ) {
			//Get the nElements's src
			ISeries source = plot.DataSource;
			while (source is ITransformedSeries)
				//If the current src is a transformation, get its src.
				source = ((ITransformedSeries)source).Source;
#if DEBUG
			//Source should be an untransformed ISeries
			Debug.Assert( !(source is ITransformedSeries) );
			Debug.Assert( source is ISeries );
#endif
			return source;
		}

		#region DP: SelectedPlot
		public static readonly DependencyProperty SelectedPlotProperty = DependencyProperty.RegisterAttached( "SelectedPlot", typeof( Plot ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, SelectedPlotChangedStatic ) );
		public Plot SelectedPlot {
			get { return (Plot)GetValue( SelectedPlotProperty ); }
			set { SetValue( SelectedPlotProperty, value ); }
		}
		public static Plot GetSelectedPlot( DependencyObject element ) { return (Plot)element.GetValue( SelectedPlotProperty ); }
		public static void SetSelectedPlot( DependencyObject element, Plot value ) { element.SetValue( SelectedPlotProperty, value ); }
		private static void SelectedPlotChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			AbstractAxes axes = sender as AbstractAxes;
		}
		#endregion

		#region DP: ActiveHighlightBrush
		public static readonly DependencyProperty ActiveHighlightBrushProperty = DependencyProperty.RegisterAttached( "ActiveHighlightBrush", typeof( Brush ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( Brushes.White, FrameworkPropertyMetadataOptions.None, ActiveHighlightBrushChangedStatic ) );
		public Brush ActiveHighlightBrush {
			get { return (Brush)GetValue( ActiveHighlightBrushProperty ); }
			set { SetValue( ActiveHighlightBrushProperty, value ); }
		}
		public static Brush GetActiveHighlightBrush( DependencyObject element ) { return (Brush)element.GetValue( ActiveHighlightBrushProperty ); }
		public static void SetActiveHighlightBrush( DependencyObject element, Brush value ) { element.SetValue( ActiveHighlightBrushProperty, value ); }
		private static void ActiveHighlightBrushChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: InactiveHighlightBrush
		public static readonly DependencyProperty InactiveHighlightBrushProperty = DependencyProperty.RegisterAttached( "InactiveHighlightBrush", typeof( Brush ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( new SolidColorBrush( Colors.White ) { Opacity = 0.25 }, FrameworkPropertyMetadataOptions.None, InactiveHighlightBrushChangedStatic ) );
		public Brush InactiveHighlightBrush {
			get { return (Brush)GetValue( InactiveHighlightBrushProperty ); }
			set { SetValue( InactiveHighlightBrushProperty, value ); }
		}
		public static Brush GetInactiveHighlightBrush( DependencyObject element ) { return (Brush)element.GetValue( InactiveHighlightBrushProperty ); }
		public static void SetInactiveHighlightBrush( DependencyObject element, Brush value ) { element.SetValue( InactiveHighlightBrushProperty, value ); }
		private static void InactiveHighlightBrushChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: SelectionMinima
		public static readonly DependencyProperty SelectionMinimaProperty = DependencyProperty.RegisterAttached( "SelectionMinima", typeof( Vector ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( new Vector(), FrameworkPropertyMetadataOptions.None, SelectionMinimaChangedStatic ) );
		public Vector SelectionMinima {
			get { return (Vector)GetValue( SelectionMinimaProperty ); }
			set { SetValue( SelectionMinimaProperty, value ); }
		}
		public static Vector GetSelectionMinima( DependencyObject element ) { return (Vector)element.GetValue( SelectionMinimaProperty ); }
		public static void SetSelectionMinima( DependencyObject element, Vector value ) { element.SetValue( SelectionMinimaProperty, value ); }
		private static void SelectionMinimaChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: SelectionMaxima
		public static readonly DependencyProperty SelectionMaximaProperty = DependencyProperty.RegisterAttached( "SelectionMaxima", typeof( Vector ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( new Vector(), FrameworkPropertyMetadataOptions.None, SelectionMaximaChangedStatic ) );
		public Vector SelectionMaxima {
			get { return (Vector)GetValue( SelectionMaximaProperty ); }
			set { SetValue( SelectionMaximaProperty, value ); }
		}
		public static Vector GetSelectionMaxima( DependencyObject element ) { return (Vector)element.GetValue( SelectionMaximaProperty ); }
		public static void SetSelectionMaxima( DependencyObject element, Vector value ) { element.SetValue( SelectionMaximaProperty, value ); }
		private static void SelectionMaximaChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: SelectionValid
		public static readonly DependencyProperty SelectionValidProperty = DependencyProperty.RegisterAttached( "SelectionValid", typeof( bool ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.None, SelectionValidChangedStatic ) );
		public bool SelectionValid {
			get { return (bool)GetValue( SelectionValidProperty ); }
			set { SetValue( SelectionValidProperty, value ); }
		}
		public static bool GetSelectionValid( DependencyObject element ) { return (bool)element.GetValue( SelectionValidProperty ); }
		public static void SetSelectionValid( DependencyObject element, bool value ) { element.SetValue( SelectionValidProperty, value ); }
		private static void SelectionValidChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			AbstractAxes axes = sender as AbstractAxes;
			if (axes != null) {
				axes._selectionPathVisual.Visibility = (axes.IsSelecting || axes.SelectionValid) ? Visibility.Visible : Visibility.Hidden;
			}
		}
		#endregion

		#region DP: IsSelecting
		public static readonly DependencyProperty IsSelectingProperty = DependencyProperty.RegisterAttached( "IsSelecting", typeof( bool ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.None, IsSelectingChangedStatic ) );
		public bool IsSelecting {
			get { return (bool)GetValue( IsSelectingProperty ); }
			set { SetValue( IsSelectingProperty, value ); }
		}
		public static bool GetIsSelecting( DependencyObject element ) { return (bool)element.GetValue( IsSelectingProperty ); }
		public static void SetIsSelecting( DependencyObject element, bool value ) { element.SetValue( IsSelectingProperty, value ); }
		private static void IsSelectingChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			AbstractAxes axes = sender as AbstractAxes;
			if (axes != null) {
				axes._selectionPathVisual.Visibility = (axes.IsSelecting || axes.SelectionValid) ? Visibility.Visible : Visibility.Hidden;
			}
		}
		#endregion

		#region DP: SelectionFill
		public static readonly DependencyProperty SelectionFillProperty = DependencyProperty.RegisterAttached( "SelectionFill", typeof( Brush ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( new SolidColorBrush( Colors.Lime ) { Opacity = 0.3 }, FrameworkPropertyMetadataOptions.None, SelectionFillChangedStatic ) );
		public Brush SelectionFill {
			get { return (Brush)GetValue( SelectionFillProperty ); }
			set { SetValue( SelectionFillProperty, value ); }
		}
		public static Brush GetSelectionFill( DependencyObject element ) { return (Brush)element.GetValue( SelectionFillProperty ); }
		public static void SetSelectionFill( DependencyObject element, Brush value ) { element.SetValue( SelectionFillProperty, value ); }
		private static void SelectionFillChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: SelectionPen
		public static readonly DependencyProperty SelectionPenProperty = DependencyProperty.RegisterAttached( "SelectionPen", typeof( Pen ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( new Pen( Brushes.Lime, 2 ), FrameworkPropertyMetadataOptions.None, SelectionPenChangedStatic ) );
		public Pen SelectionPen {
			get { return (Pen)GetValue( SelectionPenProperty ); }
			set { SetValue( SelectionPenProperty, value ); }
		}
		public static Pen GetSelectionPen( DependencyObject element ) { return (Pen)element.GetValue( SelectionPenProperty ); }
		public static void SetSelectionPen( DependencyObject element, Pen value ) { element.SetValue( SelectionPenProperty, value ); }
		private static void SelectionPenChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: SelectionGeometry
		public static readonly DependencyProperty SelectionGeometryProperty = DependencyProperty.RegisterAttached( "SelectionGeometry", typeof( RectangleGeometry ), typeof( AbstractAxes ), new FrameworkPropertyMetadata( new RectangleGeometry( new Rect( 0, 0, 1, 1 ) ), FrameworkPropertyMetadataOptions.None, SelectionGeometryChangedStatic ) );
		public RectangleGeometry SelectionGeometry {
			get { return (RectangleGeometry)GetValue( SelectionGeometryProperty ); }
			set { SetValue( SelectionGeometryProperty, value ); }
		}
		public static RectangleGeometry GetSelectionGeometry( DependencyObject element ) { return (RectangleGeometry)element.GetValue( SelectionGeometryProperty ); }
		public static void SetSelectionGeometry( DependencyObject element, Geometry value ) { element.SetValue( SelectionGeometryProperty, value ); }
		private static void SelectionGeometryChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		private ObservableCollection<Plot> _plots = new ObservableCollection<Plot>();
		public sealed override ICollection<Plot> Plots {
			get { return _plots; }
		}

		private Path _selectionPathVisual = new Path();

		protected abstract Visual SelectionShape { get; }

		public abstract ToolTip ToolTipContent {
			get;
		}

		protected abstract ITransformedSeries GetPlotDataTransform( Plot plot );

		static AbstractAxes() {
			FocusableProperty.OverrideMetadata( typeof( AbstractAxes ), new FrameworkPropertyMetadata( true ) );
		}
		protected AbstractAxes( int dimensions = 2 )
			: base( dimensions ) {

			_plots.CollectionChanged += delegate( object sender, NotifyCollectionChangedEventArgs e ) {
				if (e.NewItems != null)
					foreach (Plot obj in e.NewItems) {
						OnAddPlot( obj );
					}
				if (e.OldItems != null)
					foreach (Plot obj in e.OldItems) {
						OnRemovePlot( obj );
					}
			};

			AddVisualChild( _selectionPathVisual );
			_selectionPathVisual.Stretch = Stretch.Fill;
			_selectionPathVisual.SetBinding( Path.DataProperty, new Binding( "SelectionGeometry" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.FillProperty, new Binding( "SelectionFill" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeProperty, new Binding( "SelectionPen.Brush" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeDashCapProperty, new Binding( "SelectionPen.DashCap" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeDashOffsetProperty, new Binding( "SelectionPen.DashStyle.Offset" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeEndLineCapProperty, new Binding( "SelectionPen.EndLineCap" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeLineJoinProperty, new Binding( "SelectionPen.LineJoin" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeMiterLimitProperty, new Binding( "SelectionPen.MiterLimit" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeStartLineCapProperty, new Binding( "SelectionPen.StartLineCap" ) { Source = this } );
			_selectionPathVisual.SetBinding( Path.StrokeThicknessProperty, new Binding( "SelectionPen.Thickness" ) { Source = this } );
		}

		public sealed override void AddPlot( Plot plot ) {
			Plots.Add( plot );
		}
		protected virtual void OnAddPlot( Plot plot ) {
			plot.ClearTransforms();
			//Add the Axes-specific transformation to the nElements (e.g. map (θ,r) -> (x,y))
			plot.PushTransform( GetPlotDataTransform( plot ) );
			if (GetPlotChannels( plot ) == null)
				SetPlotChannels( plot, Enumerable.Range( 0, Dimensions ).ToArray() );

			//Set Plot style
			if (plot.PlotStyle == null) {
				plot.PlotStyle = (PlotStyle)PlotStyles[(Plots.Count - 1) % PlotStyles.Count].Clone();
			}

			plot.SetBinding( Plot.IsSelectedProperty, new Binding( "SelectedPlot" ) { Source = this, Converter = PlotSelectionConverter, ConverterParameter = plot, Mode = BindingMode.OneWay } );

			//Listen for selection events
			Plot.AddClickHandler( plot, OnPlotClicked );

			//Tell WPF that sp is our child
			if (!plot.IsDescendantOf( this ))
				AddVisualChild( plot );
		}

		public sealed override void RemovePlot( Plot plot ) {
			Plots.Remove( plot );
		}
		protected virtual void OnRemovePlot( Plot plot ) {
			ISeries source = GetOriginalSeries( plot );
			Plot.RemoveClickHandler( plot, OnPlotClicked );
			RemoveVisualChild( plot );
		}

		public override void SetZIndex( Plot plot, int zIndex ) {
			_plots.Move( _plots.IndexOf( plot ), zIndex );
		}
		public override int GetZIndex( Plot plot ) {
			return _plots.IndexOf( plot );
		}

		protected override void OnAxisSetChanged( Plot plot, int oldAxisSet, int newAxisSet ) {
			if (plot != null)
				InvalidateMeasure();
		}

		protected virtual void OnPlotClicked( object sender, RoutedEventArgs e ) {
			//if (SelectedPlot != null)
			//    SelectedPlot.IsSelected = false;

			if (SelectedPlot == sender)
				SelectedPlot = null;

			else
				SelectedPlot = sender as Plot;

			//if (SelectedPlot != null)
			//    SelectedPlot.IsSelected = true;
		}

		protected sealed override System.Windows.Size MeasureOverride( System.Windows.Size availableSize ) { return availableSize; }
		protected sealed override System.Windows.Size ArrangeOverride( System.Windows.Size finalSize ) {
			Rect pa = new Rect( finalSize );
			SetPlotArea( pa );
			if (pa.Width > 0 && pa.Height > 0) {
				foreach (Plot plot in Plots) {
					Rect loc = GetPlotLayout( plot );
					plot.Measure( loc.Size );
					plot.Arrange( loc );
				}
				if (SelectionValid) {
					pa = new Rect( ToPlotSpace( SelectionMinima ), ToPlotSpace( SelectionMaxima ) );
					_selectionPathVisual.Measure( pa.Size );
					_selectionPathVisual.Arrange( pa );
				}
			}
			return finalSize;
		}

		protected abstract void SetPlotArea( Rect graphSize );
		protected abstract Rect GetPlotLayout( Plot plot );

		protected delegate void PlotIterationCallback( Plot plot );
		protected void IteratePlots( PlotIterationCallback callback ) {
			foreach (Plot plot in Plots) {
				callback( plot );
			}
		}
		protected void IteratePlotsAsync( PlotIterationCallback callback, Delegate OnFinishCallback = null ) {
			Thread iterator = new Thread( delegate() { IteratePlots( callback ); if (OnFinishCallback != null)OnFinishCallback.DynamicInvoke(); } );
			iterator.Start();
		}

		/// <summary>
		/// Use AddSeries and RemoveSeries to adjust this nElements count
		/// </summary>
		protected sealed override int InternalPlotCount {
			get { return Plots.Count; }
		}

		/// <summary>
		/// Returns the number of Plots on these ui. If there a non-nElements children, this method will need to be overriden to account for these additional children.
		/// <see cref="GetVisualChild" /> will also need to be overriden if this method is.
		/// </summary>
		protected override int VisualChildrenCount {
			get { return Plots.Count + 1; }
		}

		/// <summary>
		/// Returns the number the <code>index</code>'th nElements on these ui.
		/// If there a non-nElements children, this method will need to be overriden to account for these additional children.
		/// <see cref="GetVisualChild" /> will also need to be overriden if this method is.
		/// </summary>
		protected override System.Windows.Media.Visual GetVisualChild( int index ) {
			return (index == _plots.Count) ? (Visual)_selectionPathVisual : _plots[index];
		}

		protected override void OnRender( System.Windows.Media.DrawingContext cx ) {
			base.DrawBackground( cx );
			DrawBackground( cx );
		}

		protected abstract void DrawBackground( DrawingContext cx );

		protected abstract Mesuro.Common.Vector ToDataSpace( Point coord );
		protected abstract Point ToPlotSpace( Mesuro.Common.Vector datum );

		Point selectionStart = new Point();
		protected override void OnMouseMove( MouseEventArgs e ) {
			base.OnMouseMove( e );
			Point mouse = e.GetPosition( this );
			if (PlotArea.FillContains( mouse )) {
				Cursor = Cursors.Cross;
				if (IsSelecting) {
					Rect r = new Rect( selectionStart, mouse );
					_selectionPathVisual.Measure( r.Size );
					_selectionPathVisual.Arrange( r );
				} else if (Mouse.LeftButton == MouseButtonState.Pressed && Mouse.RightButton == MouseButtonState.Released) {
					Point[] points = new Point[1];
					int nPoints = Mouse.GetIntermediatePoints( this, points );
					if (nPoints > 0) {
						selectionStart = points[0];

					} else {
						selectionStart = mouse;
					}
					IsSelecting = true;
				}
			} else {
				Cursor = Cursors.Arrow;
			}
		}
		protected override void OnMouseUp( MouseButtonEventArgs e ) {
			base.OnMouseUp( e );

			if (IsSelecting) {
				IsSelecting = false;
				SelectionMinima = ToDataSpace( _selectionPathVisual.TransformToAncestor( this ).Transform( new Point( 0, 0 ) ) );
				SelectionMaxima = ToDataSpace( _selectionPathVisual.TransformToAncestor( this ).Transform( new Point( _selectionPathVisual.ActualWidth, _selectionPathVisual.ActualHeight ) ) );
			} else if (Mouse.DirectlyOver == this) {
				//And Was Left mouse button
				SelectedPlot = null;
				SelectionValid = false;
			}
		}
		protected override void OnMouseDown( MouseButtonEventArgs e ) {
			base.OnMouseDown( e );
		}

		protected override void OnGotFocus( RoutedEventArgs e ) {
			base.OnGotFocus( e );
			Plot.SetHighlightBrush( this, ActiveHighlightBrush );
		}
		protected override void OnLostFocus( RoutedEventArgs e ) {
			base.OnLostFocus( e );
			Plot.SetHighlightBrush( this, InactiveHighlightBrush );
		}

		private static _PlotSelectionConverter PlotSelectionConverter = new _PlotSelectionConverter();
		private class _PlotSelectionConverter : IValueConverter {

			public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
				return value == parameter;
			}

			public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
				throw new NotImplementedException();
			}
		}
	}
}
